package main

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"flag"
	"io/ioutil"
	"net/http"
	"os"
	"os/signal"
	"path"
	"path/filepath"
	"plugin"
	"strings"
	"time"

	"gitee.com/jason_elva8325/apigw-quickstart/common"
	"gitee.com/jason_elva8325/apigw-quickstart/config"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/sd/etcdv3"
	"github.com/gorilla/mux"
)

var (
	confFile         = flag.String("conf-file", "", "Host path of TOML format config file. If this param set, other params will be lose efficacy")
	listenHost       = flag.String("listen-host", "0.0.0.0", "TCP listen host")
	httpPort         = flag.String("http-port", "", "HTTP listen port")
	httpCaCert       = flag.String("http-ca-cert", "", "HTTPS root cert file")
	httpCert         = flag.String("http-cert", "", "HTTPS cert file")
	httpKey          = flag.String("http-key", "", "HTTPS cert key file")
	verifyClientCert = flag.Bool("verify-client-cert", false, "Force verify client certification")
	srdETCDServers   = flag.String("srd-etcd-servers", "127.0.0.1:2379", "Comma-separated ETCD server addresses for Service Discovery & Registry. Example: '127.0.0.1:2379,127.0.0.1:13279'")
	srdETCDCaCert    = flag.String("srd-etcd-ca-cert", "", "ETCD client access root cert file path for Service Discovery & Registry")
	srdETCDCert      = flag.String("srd-etcd-cert", "", "ETCD client access cert file path for Service Discovery & Registry")
	srdETCDKey       = flag.String("srd-etcd-key", "", "ETCD client access cert's key file path for Service Discovery & Registry")
	srdPrefix        = flag.String("srd-prefix", "/svc/", "Service Discovery & Registry Prefix")
	adapterDir       = flag.String("adapter-dir", "", "Adapter files store location")
	gracefulTimeout  = flag.Duration("graceful-timeout", time.Duration(15)*time.Second, "Graceful shutdown timeout")
	logger           log.Logger
)

func init() {
	// 全局日志对象定义
	logger = log.NewJSONLogger(os.Stderr)
	logger = log.With(logger, "ts", log.DefaultTimestampUTC)
	logger = log.With(logger, "caller", log.DefaultCaller)
}

func main() {
	flag.Parse()

	// 生成服务配置对象
	conf := parseConfigure()

	// 生成ETCD客户端连接
	client, err := etcdv3.NewClient(context.Background(), strings.Split(conf.Common.SRDETCDServers, ","), etcdv3.ClientOptions{
		DialTimeout:   time.Second * 3,
		DialKeepAlive: time.Second * 3,
		CACert:        conf.Common.SRDETCDCaCert,
		Cert:          conf.Common.SRDETCDCert,
		Key:           conf.Common.SRDETCDKey,
	})
	if err != nil {
		logger.Log("Service regist to ETCD server fail", err)
		os.Exit(1)
	}

	// 添加日志中间件及API列表信息查询
	router := mux.NewRouter().PathPrefix("/api").Subrouter()
	router.Use(headerInitMiddleware)
	router.Use(loggingMiddleware)
	router.HandleFunc("", func(w http.ResponseWriter, r *http.Request) {
		// 利用router的walk功能遍历API接口
		var apis = make([]common.APIResume, 0)
		err := router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
			var api common.APIResume
			pathTemplate, err := route.GetPathTemplate()
			if err == nil {
				api.Route = pathTemplate
			}
			pathRegexp, err := route.GetPathRegexp()
			if err == nil {
				api.PathRegexp = pathRegexp
			}
			queriesTemplates, err := route.GetQueriesTemplates()
			if err == nil {
				api.QueriesTemplates = strings.Join(queriesTemplates, ",")
			}
			queriesRegexps, err := route.GetQueriesRegexp()
			if err == nil {
				api.QueriesRegexps = strings.Join(queriesRegexps, ",")
			}
			methods, err := route.GetMethods()
			if err == nil {
				api.Methods = strings.Join(methods, ",")
			}
			apis = append(apis, api)
			return nil
		})
		if err != nil {
			logger.Log("API Index get fail", err)
			w.WriteHeader(http.StatusExpectationFailed)
		} else {
			w.WriteHeader(http.StatusOK)
		}
		json.NewEncoder(w).Encode(apis)
	}).Methods("GET")

	// 获取adapter文件列表
	if conf.Common.AdapterDir == "" {
		dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			logger.Log("Adapter absolutly file path get fail", err)
			os.Exit(1)
		}
		conf.Common.AdapterDir = dir
	} else {
		dir, err := filepath.Abs(filepath.Dir(conf.Common.AdapterDir))
		if err != nil {
			logger.Log("Adapter absolutly file path get fail", err)
			os.Exit(1)
		}
		conf.Common.AdapterDir = dir
	}
	rd, err := ioutil.ReadDir(conf.Common.AdapterDir)
	if err != nil {
		logger.Log("Read adapters from path fail", err)
		os.Exit(1)
	}
	// 循环载入所有adapter
	var adapterCnt = 0
	for _, fi := range rd {
		if !fi.IsDir() && path.Ext(fi.Name()) == ".so" {
			p, err := plugin.Open(conf.Common.AdapterDir + "/" + fi.Name())
			if err != nil {
				logger.Log("adapter file open fail", err)
				os.Exit(1)
			}
			f, err := p.Lookup("AdapterRegistry")
			if err != nil {
				logger.Log("adapter registry func get fail", err)
				os.Exit(1)
			}
			f.(func(*mux.Router, log.Logger, etcdv3.Client, string, map[string]config.Adapter))(router, logger, client, *srdPrefix, conf.Adapters)
			adapterCnt++
		} else {
			logger.Log("ignore file in adapter path", fi.Name())
		}
	}
	logger.Log("Adapter load path", conf.Common.AdapterDir, "Load adapter count", adapterCnt)

	// 启动HTTP监听
	httpServe := &http.Server{
		WriteTimeout: time.Second * 15,
		ReadTimeout:  time.Second * 15,
		IdleTimeout:  time.Second * 60,
		Handler:      router,
	}
	// 判断是否启用SSL
	if conf.Common.HTTPCert != "" && conf.Common.HTTPKey != "" {
		// 如果没有指定HTTPS端口，默认为443端口
		if conf.Common.HTTPPort == "" {
			conf.Common.HTTPPort = "443"
		}
		httpServe.Addr = conf.Common.ListenHost + ":" + conf.Common.HTTPPort
		httpServe.TLSConfig = &tls.Config{
			MinVersion: tls.VersionTLS11,
		}
		// 判断是否开启客户端证书校验
		if conf.Common.VerifyClientCert == true {
			httpServe.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
			// 判断是否载入私有验证(根)证书
			if conf.Common.HTTPCACert != "" {
				pool := x509.NewCertPool()
				caData, err := ioutil.ReadFile(conf.Common.HTTPCACert)
				if err != nil {
					logger.Log("SSL Root Cert file load fail", err)
					os.Exit(1)
				}
				pool.AppendCertsFromPEM(caData)
				httpServe.TLSConfig.ClientCAs = pool
			}
		}
		go func() {
			if err := httpServe.ListenAndServeTLS(conf.Common.HTTPCert, conf.Common.HTTPKey); err != nil && err != http.ErrServerClosed {
				logger.Log("HTTPS serve fail", err)
				os.Exit(1)
			}
		}()
	} else {
		// 如果没有指定HTTP端口，默认为80端口
		if conf.Common.HTTPPort == "" {
			conf.Common.HTTPPort = "80"
		}
		httpServe.Addr = conf.Common.ListenHost + ":" + conf.Common.HTTPPort
		go func() {
			if err := httpServe.ListenAndServe(); err != nil && err != http.ErrServerClosed {
				logger.Log("HTTP serve fail", err)
				os.Exit(1)
			}
		}()
	}

	// 以下为优雅关闭过程
	c := make(chan os.Signal, 1)
	// 仅在接收到 SIGINT (Ctrl+C) 时才优雅的关闭
	// 捕捉到 SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) 时不做处理
	signal.Notify(c, os.Interrupt)

	// 阻塞直至接收到信号
	<-c

	// 设置优雅关闭的超时时间（秒）
	ctx, cancel := context.WithTimeout(context.Background(), conf.Common.GracefulTimeout)
	defer cancel()
	// 如果没有连接则直接关闭，否则需要等到关闭的超时时间
	httpServe.Shutdown(ctx)
	logger.Log("graceful shutdown", "true")
	os.Exit(0)
}

// 生成服务配置对象
func parseConfigure() *config.ConfigFile {
	// 当配置文件设置不为空时，优先使用配置文件内容
	if strings.Trim(*confFile, " ") != "" {
		return config.ParseConfigFile(*confFile, logger)
	}
	return &config.ConfigFile{
		Common: &config.CommonConfig{
			ListenHost:       *listenHost,
			HTTPPort:         *httpPort,
			HTTPCACert:       *httpCaCert,
			HTTPCert:         *httpCert,
			HTTPKey:          *httpKey,
			VerifyClientCert: *verifyClientCert,
			SRDETCDServers:   *srdETCDServers,
			SRDETCDCaCert:    *srdETCDCaCert,
			SRDETCDCert:      *srdETCDCert,
			SRDETCDKey:       *srdETCDKey,
			SRDPrefix:        *srdPrefix,
			AdapterDir:       *adapterDir,
			GracefulTimeout:  *gracefulTimeout,
		},
	}
}
